package ddb.tasktracker.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import ddb.tasktracker.intf.context.dto.ContextStatus;
import ddb.tasktracker.intf.context.http.FileStoreInfo;
import ddb.tasktracker.intf.context.http.FileStoreInfoArray;
import ddb.tasktracker.intf.context.http.HttpUploadFailedFile;
import ddb.tasktracker.intf.context.http.HttpUploadResult;
import ddb.tasktracker.intf.context.http.NewTaskEntity;
import ddb.tasktracker.intf.context.http.SetContextStatusEntity;
import ddb.tasktracker.intf.context.service.IContextService;
import ddb.tasktracker.intf.shared.ErrorCode;
import ddb.tasktracker.intf.shared.http.BaseResponse;
import util.lang.RefValue;

@Controller
@RequestMapping("api/v1/tasktrackercontext")
public class TrackerContextController {

	Logger logger = LoggerFactory.getLogger(TrackerContextController.class);

	@Autowired
	IContextService contextService;

	@RequestMapping(method = RequestMethod.GET, value = "status")
	public ResponseEntity<?> getContextStatus() {
		int status = contextService.getContextStatus();
		String message = ContextStatus.toString(status);
		return BaseResponse.create().message(message).extra(status).toEntity();
	}

	@RequestMapping(method = RequestMethod.POST, value = "status")
	public ResponseEntity<?> setContextStatus(HttpEntity<SetContextStatusEntity> httpEntity) {
		SetContextStatusEntity contextStatusEntity = httpEntity.getBody();
		int contextStatus = contextStatusEntity.getStatus();

		if (ContextStatus.toString(contextStatus) == null)
			return BaseResponse.create(ErrorCode.INVALID_PARAMETERS).toEntity();

		if (contextStatus != ContextStatus.SHUTTING_DOWN && contextStatus != ContextStatus.WORKING)
			return BaseResponse.create(ErrorCode.INVALID_OPERATION).toEntity();
		
		contextService.setContextStatus(contextStatus);

		return BaseResponse.create().toEntity();
	}
	
	@RequestMapping(method = RequestMethod.PUT, value = "task")
	public ResponseEntity<?> newTask(HttpEntity<NewTaskEntity> httpEntity) {
		NewTaskEntity task = httpEntity.getBody();

		// TODO 参数验证

		RefValue<Integer> errCode = new RefValue<Integer>();
		String guid = contextService.newTask(task.getName(), task.getTrackerName(), task.getOwner(),
				task.getPropertiesMap(), errCode);

		return BaseResponse.create(errCode.getValue()).extra(guid).toEntity();
	}



	@RequestMapping(method = RequestMethod.PUT, value = "task/storeInputFiles/{guid}")
	public ResponseEntity<?> storeInputFiles(@PathVariable("guid") String guid,
			@RequestParam(value = "file[]", required = false) MultipartFile[] files) {
		if (files == null || files.length == 0)
			return BaseResponse.create().extra(HttpUploadResult.empty()).toEntity();

		// TODO 检测GUID合法性

		List<String> successFiles = new ArrayList<String>();
		List<HttpUploadFailedFile> failedFiles = new ArrayList<HttpUploadFailedFile>();

		// 保存上传的文件
		File inputBase = contextService.acquireTaskInputFileStore(guid, true);
		for (int i = 0; i < files.length; i++) {
			MultipartFile multipartFile = files[i];
			String fileName = getFileName(multipartFile.getOriginalFilename());
			try {
				IOUtils.copy(multipartFile.getInputStream(),
						new FileOutputStream(new File(inputBase, fileName), false));
				successFiles.add(fileName);
			} catch (IllegalStateException e) {
				logger.warn("storeInputFiles : This error should never occurred.", e);
				failedFiles.add(new HttpUploadFailedFile(fileName, e.getMessage()));
			} catch (IOException e) {
				logger.error("storeInputFiles Task[" + guid + "]:file[" + fileName + "]", e);
				failedFiles.add(new HttpUploadFailedFile(fileName, e.getMessage()));
			}
		}

		// 返回结果
		// http://stackoverflow.com/questions/4042434/converting-arrayliststring-to-string-in-java
		HttpUploadResult result = new HttpUploadResult(successFiles.toArray(new String[0]),
				failedFiles.toArray(new HttpUploadFailedFile[0]));
		return BaseResponse.create().extra(result).toEntity();
	}

	@RequestMapping(method = RequestMethod.GET, value = "task/getInputFilesList/{guid}")
	public ResponseEntity<?> getAllInputFiles(@PathVariable("guid") String guid) {

		// TODO 检测GUID合法性

		// 检查输入文件是否存在
		File inputBase = contextService.acquireTaskInputFileStore(guid, false);
		if (!inputBase.exists()) {
			return BaseResponse.create().extra(FileStoreInfoArray.empty()).toEntity();
		}

		// 遍历文件
		List<FileStoreInfo> resultFiles = new ArrayList<FileStoreInfo>();
		File[] files = inputBase.listFiles();
		for (File file : files) {
			resultFiles.add(new FileStoreInfo(file.getName(), file.length()));
		}

		// 返回结果
		return BaseResponse.create().extra(FileStoreInfoArray.create(resultFiles)).toEntity();
	}

	@RequestMapping(method = RequestMethod.GET, value = "task/getInputFile/{guid}/{fileName}")
	public void getInputFile(@PathVariable String guid, @PathVariable String fileName, HttpServletResponse response)
			throws IOException {

		if (fileName != null)
			fileName = decodeFileName(fileName);

		// TODO 检测GUID合法性
		// TODO 检测FileName合法性（安全性考虑）

		File inputBase = contextService.acquireTaskInputFileStore(guid, false);
		File targetFile = new File(inputBase, fileName);

		if (!targetFile.exists()) {
			response.sendError(404);
			return;
		}

		response.setContentLengthLong(targetFile.length());
		response.setContentType("application/octet-stream");
		response.setHeader("Content-Disposition",
				"attachment; filename=\"" + URLEncoder.encode(targetFile.getName(), "UTF-8") + "\"");
		try (InputStream fileStream = new FileInputStream(targetFile)) {
			IOUtils.copy(fileStream, response.getOutputStream());
		}

	}

	@RequestMapping(method = RequestMethod.DELETE, value = "task/removeInputFiles/{guid}/{fileName}")
	public ResponseEntity<?> removeInputFile(@PathVariable("guid") String guid,
			@PathVariable("fileName") String fileName) {

		if (fileName != null)
			fileName = decodeFileName(fileName);

		// TODO 检测GUID合法性
		// TODO 检测FileName合法性（安全性考虑）

		File inputBase = contextService.acquireTaskInputFileStore(guid, false);
		File targetFile = new File(inputBase, fileName);

		if (targetFile.exists())
			targetFile.delete();

		return BaseResponse.create().toEntity();
	}

	@RequestMapping(method = RequestMethod.POST, value = "task/commit/{guid}")
	public ResponseEntity<?> commitTask(@PathVariable("guid") String guid) {

		// TODO 参数检测

		RefValue<Integer> errCode = new RefValue<Integer>();
		contextService.commitTask(guid, errCode);

		return BaseResponse.create(errCode.getValue()).toEntity();
	}


	@RequestMapping(method = RequestMethod.GET, value = "task/getOutputFilesList/{guid}")
	public ResponseEntity<?> getAllOutputFiles(@PathVariable("guid") String guid) {

		// TODO 检测GUID合法性

		// 检查输入文件是否存在
		File outputBase = contextService.acquireTaskOutputFileStore(guid, false);
		if (!outputBase.exists()) {
			return BaseResponse.create().extra(FileStoreInfoArray.empty()).toEntity();
		}

		// 遍历文件
		List<FileStoreInfo> resultFiles = new ArrayList<FileStoreInfo>();
		File[] files = outputBase.listFiles();
		for (File file : files) {
			resultFiles.add(new FileStoreInfo(file.getName(), file.length()));
		}

		// 返回结果
		return BaseResponse.create().extra(FileStoreInfoArray.create(resultFiles)).toEntity();
	}

	@RequestMapping(method = RequestMethod.GET, value = "task/getOutputFile/{guid}/{fileName}")
	public void getOutputFile(@PathVariable String guid, @PathVariable String fileName, HttpServletResponse response)
			throws IOException {

		if (fileName != null)
			fileName = decodeFileName(fileName);

		// TODO 检测GUID合法性
		// TODO 检测FileName合法性（安全性考虑）

		File outputBase = contextService.acquireTaskOutputFileStore(guid, false);
		File targetFile = new File(outputBase, fileName);

		if (!targetFile.exists()) {
			response.sendError(404);
			return;
		}

		response.setContentLengthLong(targetFile.length());
		response.setContentType("application/octet-stream");
		response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(targetFile.getName(), "UTF-8") + "\"");
		try (InputStream fileStream = new FileInputStream(targetFile)) {
			IOUtils.copy(fileStream, response.getOutputStream());
		}
		
	}

	private static String getFileName(String path) {
		if (path == null)
			return null;
		int iPos1 = path.lastIndexOf('\\');
		int iPos2 = path.lastIndexOf('/');
		int iPos = (iPos1 > iPos2) ? iPos1 : iPos2;
		if (iPos < 0)
			return path;
		return path.substring(iPos + 1);
	}

	public static String encodeFileName(String fileName) {
		return fileName.replace("`", "``").replace("+", "`p").replace(".", "`d").replace(" ", "`e");
	}

	public static String decodeFileName(String fileName) {
		return fileName.replace("`e", " ").replace("`d", ".").replace("`p", "+").replace("``", "`");
	}

}
